<script lang="ts" module>
    export const CarouselContextKey = Symbol("CarouselContextKey");
    export const useCarouselContext = () => getContext(CarouselContextKey);
</script>

<script lang="ts">
    import { getContext, onDestroy, onMount, setContext, untrack } from "svelte";
    import { useClassList } from "../utils/useClassList";
    import type { CarouselProps, CarouselStore } from "./Carousel";
    import { useStyle } from "../utils/useStyle";
    import Icon from "../Common/Icons/Icon.svelte";
    import { Space } from "../Space";
    import InnerCarouselItem from "./InnerCarouselItem.svelte";

    let { activeIndex = $bindable(0), ...props }: CarouselProps = $props();
    let current = $state(activeIndex);

    const arrow = $derived(props.arrow ?? false);
    const dotType = $derived(props.dotType ?? "dot");
    const autoPlay = $derived(props.autoPlay ?? false);
    const duration = $derived(props.duration ?? 4000);
    const draggable = $derived(props.draggable ?? false);
    const effectName = $derived(props.effect ?? "slide");
    const itemsPerView = $derived(props.itemsPerView ?? 1);
    const dir = $derived(props.dir ?? "h");
    const dotColor = $derived(props.dotColor ?? "rgba(var(--cui-grey-8), 0.3)");
    const dotActiveColor = $derived(props.dotActiveColor ?? "rgba(var(--cui-white), 1)");
    const dotAlign = $derived(props.dotAlign ?? (dir === "h" ? "bottom" : "right"));
    const gutter = $derived(props.gutter ?? 0);

    const classList = $derived(
        useClassList(props, "cm-carousel", {
            "cm-carousel-views-auto": itemsPerView === "auto",
            [`cm-carousel-${effectName}`]: effectName,
            [`cm-carousel-${dir}`]: dir,
        }),
    );

    let wrap: any;
    let list: any;
    let playTimer: any = null;
    let inited = false;
    const arrowClasses = $derived({
        "cm-carousel-arrow": true,
        [`cm-carousel-arrow-${arrow}`]: arrow,
    });

    const arrowsClasses = $derived({
        "cm-carousel-actions": true,
        "cm-carousel-actions-with-arrow": arrow,
        [`cm-carousel-actions-${dotAlign}`]: !!dotAlign,
    });

    const dotClasses = $derived({
        "cm-carousel-dots": true,
        [`cm-carousel-dots-${dotType}`]: !!dotType,
        [`cm-carousel-dots-${dotAlign}`]: !!dotAlign,
    });

    const store = $state({
        data: [],
        activeIndex: activeIndex,
        unActiveIndex: -1,
        activeKey: "",
        unActiveKey: "",
        nextKey: "",
        prevKey: "",
        startPos: 0,
        isSwiping: false,
        currentPos: 0,
        dir: "normal",
    } as CarouselStore);

    let views = $state<number[]>([]);
    const loop = $derived((effectName === "slide" && itemsPerView === 1) || effectName === "card" || effectName === "fade");
    let listStyle = $state("");
    const getWidth = $derived(wrap ? (dir === "h" ? wrap.getBoundingClientRect().width : wrap.getBoundingClientRect().height) : 0);

    // 获取子元素的总长度
    const getItemsWidth = () => {
        if (list) {
            const items: any[] = list.querySelectorAll(".cm-carousel-item");
            const allW = Array.from(items).reduce((a, b) => {
                return (a += dir === "h" ? b.getBoundingClientRect().width : b.getBoundingClientRect().height);
            }, 0);
            return allW + gutter * (items.length - 1);
        }
        return 0;
    };

    $effect(() => {
        if (!list) {
            return;
        }

        if (effectName === "slide") {
            slideEffect();
        } else {
            listStyle = "";
        }
    });

    $effect(() => {
        current = activeIndex % views.length;
    });

    const slideEffect = () => {
        const index = current;
        dir;

        if (index === views.length) {
            list.style.transitionDuration = "0ms";
            let offset = 0;
            untrack(() => {
                if (store.isSwiping && store.activeIndex === views.length - 1) {
                    offset = store.currentPos - store.startPos;
                }
                list.style.transform = dir === "h" ? `translateX(${offset}px)` : `translateY(${offset}px)`;
            });
        }
        if (index === -1) {
            const w = getWidth + gutter;
            list.style.transitionDuration = "0ms";
            let offset = 0;
            untrack(() => {
                if (store.isSwiping && store.activeIndex === 0) {
                    offset = store.currentPos - store.startPos;
                }
                list.style.transform = dir === "h" ? `translateX(${-(views.length + 1) * w + offset}px)` : `translateY(${-(views.length + 1) * w + offset}px)`;
            });
        }
        untrack(() => {
            transToActive();
        });
    };

    const transToActive = () => {
        if (effectName !== "slide") {
            return;
        }
        // setTimeout(() => {
        const index = activeIndex + (loop ? 1 : 0);
        const w = getWidth + gutter;
        const duration = !inited ? 0 : 300;
        let offset = index * w;
        const allW = getItemsWidth();
        // 当前视窗超过了总长度
        if (offset + getWidth > allW) {
            offset = offset - getWidth + (allW % getWidth);
        }

        const transform = dir === "h" ? `translateX(${-offset}px)` : `translateY(${-offset}px)`;
        listStyle = useStyle(
            {},
            {
                gap: `${gutter}px`,
                "transition-duration": `${duration}ms`,
                transform: transform,
            },
        );
        list.setAttribute("data-offset", -offset);
        inited = true;
        // });
    };

    // 播放
    const play = () => {
        clearTimeout(playTimer);
        onNext();
        playTimer = setTimeout(() => {
            play();
        }, duration);
    };

    onMount(() => {
        if (wrap) {
            // 自定义width在effect里面会有延迟
            setTimeout(() => {
                if (itemsPerView === "auto") {
                    const w = getWidth;
                    const allW = getItemsWidth();
                    const viewsNum = Math.ceil(allW / w);
                    if (w) {
                        views = new Array(viewsNum).fill(1);
                    }
                } else {
                    views = new Array(Math.ceil(store.data.length / itemsPerView)).fill(1);
                }
            });

            if (autoPlay) {
                playTimer = setTimeout(() => {
                    play();
                }, duration);
            }
        }
    });

    onDestroy(() => {
        if (playTimer) {
            clearTimeout(playTimer);
        }
    });

    $effect(() => {
        if (store.unActiveIndex >= 0 && (effectName === "fade" || effectName === "card")) {
            store.unActiveKey = store.data[store.unActiveIndex].id;
        }
    });

    $effect(() => {
        const last = store.activeIndex;
        const index = activeIndex;
        store.activeIndex = activeIndex;
        if (last !== activeIndex) {
            store.unActiveIndex = last;
        }
        if (views.length && (effectName === "fade" || effectName === "card")) {
            store.prevKey = store.data[(views.length + index - 1) % views.length].id;
            store.activeKey = store.data[index].id;
            store.nextKey = store.data[(index + 1) % views.length].id;
        }
    });

    const onNext = () => {
        store.unActiveIndex = store.activeIndex;
        if (loop) {
            current = store.activeIndex + 1;
            activeIndex = (store.activeIndex + 1) % views.length;
        } else {
            const lastIndex = store.activeIndex;
            const cur = Math.min(lastIndex + 1, views.length - 1);
            current = cur;
            activeIndex = cur;
            if (cur === lastIndex && draggable) {
                transToActive();
            }
        }
        props.onchange?.(activeIndex);
    };

    const onPrev = () => {
        store.unActiveIndex = store.activeIndex;
        if (loop) {
            current = store.activeIndex - 1;
            activeIndex = (views.length + store.activeIndex - 1) % views.length;
        } else {
            const lastIndex = store.activeIndex;
            const cur = Math.max(store.activeIndex - 1, 0);
            current = cur;
            activeIndex = cur;
            if (cur === lastIndex && draggable) {
                transToActive();
            }
        }
        props.onchange?.(activeIndex);
    };

    const onDotClick = (num: number) => {
        store.unActiveIndex = store.activeIndex;
        current = num;
        activeIndex = num;
        props.onchange?.(activeIndex);
    };

    const onSwipe = (swipe: any) => {
        if (effectName === "slide") {
            listStyle = useStyle(
                {},
                {
                    gap: `${gutter}px`,
                    "transition-duration": `0ms`,
                    transform: dir === "h" ? `translateX(${store.startPos - swipe.distanceX()}px)` : `translateY(${store.startPos - swipe.distanceY()}px)`,
                },
            );
        }
        store.currentPos = store.startPos - (dir === "h" ? swipe.distanceX() : swipe.distanceY());
    };

    const onSwipeStart = () => {
        const offset = list.getAttribute("data-offset");
        store.isSwiping = true;
        store.startPos = parseFloat(offset);
    };

    const onSwipeEnd = (direction: string, duration: number) => {
        if (duration && duration > 500) {
            effectName === "slide" ? transToActive() : false;
            return;
        }

        if (dir === "h") {
            if (direction === "right") {
                onPrev();
            } else if (direction === "left") {
                onNext();
            } else {
                effectName === "slide" ? transToActive() : false;
            }
        }
        if (dir === "v") {
            if (direction === "down") {
                onPrev();
            } else if (direction === "up") {
                onNext();
            } else {
                effectName === "slide" ? transToActive() : false;
            }
        }
        setTimeout(() => {
            store.isSwiping = false;
        });
    };

    const style = $derived(useStyle(props, { height: (props.height ?? 250) + "px" }));

    setContext(CarouselContextKey, {
        getStore: () => store,
        effectName: () => effectName,
        itemsPerView,
        onSwipe,
        onSwipeStart,
        onSwipeEnd,
        draggable,
        addItem: (item: any) => {
            store.data.push(item);
        },
        removeItem: (id: string) => {
            store.data = store.data.filter((item) => item.id !== id);
        },
    });
</script>

{@render props.children?.()}
<div class={classList} {style} bind:this={wrap}>
    <div class={arrowsClasses}>
        <ul
            class={dotClasses}
            style={`
                    --cui-carousel-dot-color: ${dotColor};
                    --cui-carousel-active-dot-color: ${dotActiveColor}
                `}
        >
            {#each views as item, index (index)}
                {@const dotClass = { "cm-carousel-dot": true, "cm-carousel-dot-active": store.activeIndex === index }}
                <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
                <!-- svelte-ignore a11y_click_events_have_key_events -->
                <li
                    class={dotClass}
                    onclick={() => {
                        onDotClick(index);
                    }}
                ></li>
            {/each}
        </ul>
        {#if arrow}
            <Space dir={dotAlign === "bottom" || dotAlign === "top" ? "h" : "v"}>
                <!-- svelte-ignore a11y_click_events_have_key_events -->
                <!-- svelte-ignore a11y_no_static_element_interactions -->
                <div class={arrowClasses} x-placement="left" onclick={onPrev}>
                    {#if dir === "h"}
                        <Icon icon="chevronLeft" />
                    {:else}
                        <Icon icon="chevronUp" />
                    {/if}
                </div>
                <div class={arrowClasses} x-placement="right" onclick={onNext}>
                    {#if dir === "h"}
                        <Icon icon="chevronRight" />
                    {:else}
                        <Icon icon="chevronDown" />
                    {/if}
                </div>
            </Space>
        {/if}
    </div>
    <div class="cm-carousel-list" bind:this={list} style={listStyle}>
        {#if loop}
            <InnerCarouselItem index={-1} data={store.data[store.data.length - 1]} {...store.data[store.data.length - 1]} />
        {/if}
        {#each store.data as item, index (item.id)}
            <InnerCarouselItem {index} data={item} {...item} />
        {/each}
        {#if loop}
            <InnerCarouselItem index={store.data.length} data={store.data[0]} {...store.data[0]} />
        {/if}
    </div>
</div>
